import { v4 as uuidv4 } from 'uuid';
import { ReportParams, TypeTracker } from './types';
import Utils from './utils';
import createDb, { DBUtils } from './dBUtils';
// 上报队列配置
interface QueueConfig {
	reportInterval: number; // 上报间隔
	batchSize: number; // 每批上报数量
}

import { ReportType } from './constant';
export default class Tracker {
	db: DBUtils = createDb();
	tracker: TypeTracker;
	// 采样率
	sampleRate = {
		perf: 0.5,
		resourcesPerf: 0.5,
		xhr: 0.5,
		pv: 1,
		pageDwellTime: 1,
		exposure: 1,
		automaticBurialPoint: 1,
		behaviorRecorder: 1,
	};
	queueConfig: QueueConfig;
	timer: NodeJS.Timeout | null = null;
	constructor(tracker: TypeTracker) {
		if (tracker.sampleRate) {
			this.sampleRate = { ...this.sampleRate, ...tracker.sampleRate };
		}
		this.queueConfig = {
			reportInterval: tracker.reportInterval || 10,
			batchSize: tracker.batchSize || 20,
		};
		this.tracker = tracker;
		this.setupBeforeUnloadHandler();
	}

	setupBeforeUnloadHandler() {
		window.addEventListener('beforeunload', () => {
			// 页面关闭时，将队列中的数据上报
			this.getDbLists(-1); // 获取所有数据
		});
		document.addEventListener('visibilitychange', () => {
			// 页面不可见时，将队列中的数据上报
			if (document.visibilityState === 'hidden') {
				// 获取所有数据
				// 预留DB插入延迟
				setTimeout(() => {
					this.getDbLists(-1);
				}, 1000);
			}
		});
	}

	getCommonData() {
		const browserInfo = Utils.getBrowserInfo();
		let userId = localStorage.getItem('MONITOR_SDK_USERID');
		let uuid = localStorage.getItem('MONITOR_SDK_UUID'); // 用于登录后通过userId关联
		let trackerId = sessionStorage.getItem('MONITOR_SDK_TRACKERID'); // 用户链路追踪ID
		if (!trackerId) {
			trackerId = uuidv4();
			sessionStorage.setItem('MONITOR_SDK_TRACKERID', trackerId);
		}
		if (!uuid) {
			uuid = uuidv4();
			localStorage.setItem('MONITOR_SDK_UUID', uuid);
		}
		if (!userId) {
			userId = uuid;
		}

		return {
			...browserInfo,
			timestamp: Date.now(),
			pageUrl: encodeURIComponent(location.href),
			title: document.title,
			userId,
			uuid,
			trackerId,
		};
	}

	// 自定义上报参数格式化
	reportParamsFormat(params: ReportParams): ReportParams {
		if (this.tracker.reportParamsFormat) {
			return this.tracker.reportParamsFormat(params);
		}
		return params;
	}
	//上报参数之前
	reportUploadBefore(params: Array<ReportParams>) {
		this.tracker.reportUploadBefore && this.tracker.reportUploadBefore(params);
	}

	//上报参数之后
	reportUploadAfter(params: Array<ReportParams>) {
		this.tracker.reportUploadAfter && this.tracker?.reportUploadAfter(params);
	}

	// 定义类型与采样率字段的映射关系
	private getRateKeyByType(type: string): keyof typeof this.sampleRate | null {
		const typeMap: Record<string, keyof typeof this.sampleRate> = {
			[ReportType.PERFORMANCE]: 'perf',
			[ReportType.RES_PERFORMANCE]: 'resourcesPerf',
			[ReportType.XHR]: 'xhr',
			[ReportType.PV]: 'pv',
			[ReportType.PAGE_DWELL_TIME]: 'pageDwellTime',
			[ReportType.EXPOSURE]: 'exposure',
			[ReportType.AUTOMATIC_BURIAL_POINT]: 'automaticBurialPoint',
			[ReportType.BEHAVIOR_RECORDER]: 'behaviorRecorder',
		};

		return typeMap[type] || null;
	}

	// 采样检查
	shouldSample(params: ReportParams): boolean {
		const rateKey = this.getRateKeyByType(params.type); // 获取采样率字段

		// 如果没有对应的采样率配置，默认上报
		if (!rateKey) return true;
		const rate = this.sampleRate[rateKey];
		const sample = Math.random();
		const check = rate === 1 || sample <= rate;
		if (!check && this.tracker.debug) {
			Utils.log('debug', `事件 ${params.type} 未命中采样率 ${rate * 100}%`);
		}
		return check;
	}

	// 检测最近30分钟内是否有上报过
	async isRecentlyReported(hash: string): Promise<boolean> {
		return await this.db.existsWithin30Minutes(hash);
	}

	// 存入IndexDB队列
	async pushToQueue(params: ReportParams) {
		const hash = Utils.generateUniqueHash(params) as string;

		if (await this.isRecentlyReported(hash)) {
			Utils.log('warn', `此数据最近30分钟内已经上报过，无需要重复上报`);
			return;
		}
		if (
			params.type === ReportType.LONG_TASK ||
			params.type === ReportType.JS_ERROR ||
			params.type === ReportType.VUE_ERROR ||
			params.type === ReportType.REACT_ERROR ||
			params.type === ReportType.PROMISE_ERROR ||
			params.type === ReportType.RESOURCES_ERROR ||
			params.type === ReportType.BLANKSCREEN ||
			(params.type === ReportType.XHR && params.subType === 'error')
		) {
			if (this.tracker.debug) {
				Utils.log('debug', `数据实时上报，不存入队列`);
			}
			this.report(params);
			this.db.insertCache([hash]);
		} else if (this.shouldSample(params)) {
			if (this.tracker.debug) {
				Utils.log('debug', `命中采样率，则存入队列`);
			}
			// 如果命中采样率，则存入队列

			// 插入时会根据上报参数生成hash唯一索引
			// 如果索引重复，会插人失败
			this.db.insert(params);

			// 如果队列中有数据，定时任务还未开始,则开始定时任务
			if (!this.timer) {
				this.pullFromQueue();
			}
		}
	}

	async getDbLists(batchSize = 20) {
		try {
			let result = [];
			if (batchSize === -1) {
				result = await this.db.getAll();
			} else {
				result = await this.db.getLimit(batchSize);
			}
			const queues: Array<ReportParams> = [];
			result.forEach(item => {
				item.data = JSON.parse(item.data);
				item.data.id = item.id;
				item.data.hash = item.hash;
				queues.push(item.data);
			});
			if (queues.length > 0) {
				this.report(queues);
			} else {
				if (this.timer) {
					Utils.log('debug', `所有数据已上报，停止轮询`);
					clearInterval(this.timer);
				}
			}
		} catch (error) {
			Utils.log('error', `IndexDB未获取到数据`, error);
		}
	}

	// 从IndexDB队列中取出数据上报
	pullFromQueue() {
		if (this.timer) {
			clearInterval(this.timer);
		}
		Utils.log('debug', '开启定时上报任务执行');
		this.timer = setInterval(() => {
			Utils.log('debug', '定时上报任务执行');
			// 获取队列中前[batchSize]条数据
			this.getDbLists(this.queueConfig.batchSize);
		}, this.queueConfig.reportInterval * 1000);
	}

	sendAJAX(params: Array<ReportParams>) {
		if (this.tracker.debug) {
			Utils.log('debug', 'debug模式不上报参数打印控制台', params);
			this.reportUploadAfter(params);
			this.deleteFromQueue(params);
			return;
		}
		const xhr = new XMLHttpRequest();
		xhr.open('POST', this.tracker.reportUrl, true);
		xhr.setRequestHeader('Content-Type', 'application/json');

		xhr.onreadystatechange = () => {
			if (xhr.readyState === 4) {
				if (xhr.status === 200) {
					this.reportUploadAfter(params);
					this.deleteFromQueue(params);
				}
			}
		};

		xhr.onerror = () => {
			Utils.log('error', `请求发生错误`);
		};

		try {
			xhr.send(JSON.stringify(params));
		} catch (e: any) {
			Utils.log('error', `发送异常: ${e?.message}`, e);
		}
	}

	sendBeacon(params: Array<ReportParams>) {
		if (this.tracker.debug) {
			Utils.log('debug', 'debug模式不上报参数打印控制台', params);
			this.reportUploadAfter(params);
			this.deleteFromQueue(params);
			return;
		}
		try {
			const payload = JSON.stringify(params);
			const blob = new Blob([payload], { type: 'application/json' });
			const sendResult = navigator.sendBeacon(this.tracker.reportUrl, blob);
			if (sendResult) {
				this.reportUploadAfter(params);
				this.deleteFromQueue(params);
			}
		} catch (e: any) {
			Utils.log('error', `请求发生错误`, e);
		}
	}

	// 从DB从删除已经上报的数据
	deleteFromQueue(params: Array<ReportParams>) {
		const ids: Array<string> = [];
		const hash: Array<string> = [];
		params.forEach(item => {
			if (item.id) {
				ids.push(item.id);
			}
			if (item.hash) {
				hash.push(item.hash);
			}
		});
		this.db.deleteByIds(ids);
		this.db.insertCache(hash);
	}
	async report(params: Array<ReportParams> | ReportParams, isBeacon = false) {
		let list = Array.isArray(params) ? params : [params];
		const common = this.getCommonData();
		list = list.map(item => {
			// 合并公共参数，自定义参数
			return {
				...common,
				...this.reportParamsFormat(item),
			};
		});

		// 上报前的处理
		this.reportUploadBefore(list);
		if (this.tracker.reportUrl) {
			if (isBeacon) {
				this.sendBeacon(list);
			} else {
				this.sendAJAX(list);
			}
		} else {
			Utils.log('warn', '请指定上传的URL地址');
		}
	}
}
